
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Engine/XML/Parser.h>
#include <Liberty3D/Engine/XML/Serializer.h>
#include <Liberty3D/Engine/Uuid.h>

using namespace Liberty::Engine;
using namespace Liberty::Engine::Xml;

#include <stdio.h>

#ifdef WIN32
#include <locale.h>

#define locale_t _locale_t
#else
#include <xlocale.h>
#endif

#if defined(__LP64__)
#define LY_IN64 long
#else
#define LY_IN64 long long
#endif

namespace Liberty { namespace Engine {
    locale_t getCLocale() {
        static locale_t loc = NULL;
        
        if (loc == NULL) {
#ifdef WIN32
            loc = _create_locale(LC_ALL, "C");
#else
            loc = newlocale(LC_ALL_MASK, "C", NULL);
#endif
        }
        
        return loc;
    }
}}

Writer::Writer() {
    _status = Normal;
    _statuses.push_back(Normal);

    _dictionaryPair = 0;
    _currentObject  = 0;
    _objectIt       = 1;
    _doc            = Parser::parseString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<objects/>");
    _current        = _doc->firstChildElement("objects");
    _stack.push_back(_current);
    _objects.push_back(std::shared_ptr<ISerializable>());
}

std::shared_ptr<INode> Writer::getValue(const char* name, bool clear, bool create) {
    if (name == nullptr) {
        if (_status == Collection) {
            _dictionaryPair++;
            return _current->addElement("entry");
        }
        else if (_status == Dictionary) {
            _dictionaryPair++;
            return _current->addElement(_dictionaryPair % 2? "key": "value");
        }

        throw std::exception();
    }

    std::string            nameC(name);
    std::shared_ptr<INode> n(_current->firstChildElement(nameC));
    
    if (!n) {
        if (create) {
            n = _current->addElement(nameC);
        }
    }
    else if (clear) {
        std::vector< std::shared_ptr<INode> > v;
        
        _current->childs(v);
        
        std::vector< std::shared_ptr<INode> >::iterator it, end(v.end());
        
        for (it = v.begin(); it != end; ++it) {
            _current->removeChild(*it);
        }
    }
    
    return n;
}

bool Writer::isReading() const {
    return false;
}

bool Writer::serialize(const char* name, bool& v, bool, bool mandatory) {
    if (name != nullptr && mandatory) {
        _current->setAttribute(name, v? "true": "false");
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
    
        n->setAttribute("type", "bool");
        n->addText(v? "true": "false");
    }
    
    return true;
}

bool Writer::serialize(const char* name, int& v, int, bool mandatory) {
    char s[32];
    
#ifdef WIN32
    _sprintf_s_l(s, 32, "%i", getCLocale(), v);
#else
    snprintf_l(s, 32, getCLocale(), "%i", v);
#endif
    if (name != nullptr && mandatory) {
        _current->setAttribute(name, s);
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
        
        n->setAttribute("type", "int");
        n->addText(s);
    }
    
    return true;
}

bool Writer::serialize(const char* name, unsigned int& v, unsigned int, bool mandatory) {
    char s[32];
    
#ifdef WIN32
    _sprintf_s_l(s, 32, "%u", getCLocale(), v);
#else
    snprintf_l(s, 32, getCLocale(), "%u", v);
#endif

    if (name != nullptr && mandatory) {
        _current->setAttribute(name, s);
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
        
        n->setAttribute("type", "uint");
        n->addText(s);
    }
    
    return true;
}

bool Writer::serialize(const char* name, LY_IN64& v, LY_IN64 dv, bool mandatory) {
    char s[48];
    
#if defined(WIN32)
    _i64toa_s(v, s, _countof(s), 10);
#elif defined(__LP64__)
    snprintf_l(s, 48, getCLocale(), "%li", v);
#else
    snprintf_l(s, 48, getCLocale(), "%lli", v);
#endif

    if (name != nullptr && mandatory) {
        _current->setAttribute(name, s);
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
        
        n->setAttribute("type", "long");
        n->addText(s);
    }
    
    return true;
}

bool Writer::serialize(const char* name, unsigned LY_IN64& v, unsigned LY_IN64, bool mandatory) {
    char s[48];
    
#if defined(WIN32)
    _ui64toa_s(v, s, _countof(s), 10);
#elif defined(__LP64__)
    snprintf_l(s, 48, getCLocale(), "%lu", v);
#else
    snprintf_l(s, 48, getCLocale(), "%llu", v);
#endif

    if (name != nullptr && mandatory) {
        _current->setAttribute(name, s);
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
        
        n->setAttribute("type", "ulong");
        n->addText(s);
    }
    
    return true;
}

bool Writer::serialize(const char* name, float& v, float dv, bool mandatory) {
    char s[64];
    
#if defined(WIN32)
    _sprintf_s_l(s, 64, "%f", getCLocale(), v);
#elif defined(__LP64__)
    snprintf_l(s, 64, getCLocale(), "%f", v);
#endif

    if (name != nullptr && mandatory) {
        _current->setAttribute(name, s);
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
        
        n->setAttribute("type", "float");
        n->addText(s);
    }
    
    return true;
}

bool Writer::serialize(const char* name, double& v, double dv, bool mandatory) {
    char s[64];
    
#if defined(WIN32)
    _sprintf_s_l(s, 64, "%f", getCLocale(), v);
#elif defined(__LP64__)
    snprintf_l(s, 64, getCLocale(), "%f", v);
#endif

    if (name != nullptr && mandatory) {
        _current->setAttribute(name, s);
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
        
        n->setAttribute("type", "double");
        n->addText(s);
    }
    
    return true;
}

bool Writer::serialize(const char* name, std::string& v, const std::string&, bool mandatory) {
    if (name != nullptr && mandatory) {
        _current->setAttribute(name, v);
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
    
        n->addText(v);
    }
    
    return true;
}

bool Writer::serialize(const char* name, Uuid& v, bool mandatory) {
    if (name != nullptr && mandatory) {
        _current->setAttribute(name, v.toString());
    }
    else {
        std::shared_ptr<INode> n(getValue(name, true, true));
        
        n->setAttribute("type", "uuid");
        n->addText(v.toString());
    }
    
    return true;
}

void Writer::writeObject(const char* name, const std::shared_ptr<ISerializable>& o) {
    size_t objectIndex = 0;

    if (o) {
        std::vector<std::shared_ptr<ISerializable>>::const_iterator it(_objects.begin()), end(_objects.end());

        for (; it != end; ++it, ++objectIndex) {
            if (*it == o) {
                break;
            }
        }

        if (it == end) {
            _objects.push_back(o);
        }
    }

    if (objectIndex != 1) {
        char s[32];

        if (objectIndex) {
#ifdef WIN32
            _sprintf_s_l(s, 32, "%08lx", getCLocale(), objectIndex);
#else
            snprintf_l(s, 32, getCLocale(), "%08lx", objectIndex);
#endif
        }

        std::shared_ptr<INode> n(getValue(name, true, true));
        n->setAttribute("type", "ref");

        if (objectIndex) {
            n->addText(s);
        }
    }

    writePendingObjects();
}

void Writer::writePendingObjects() {
    if ((_currentObject == 0) && (_stack.size() == 1)) {
        while (_objectIt < _objects.size()) {
            _currentObject = _objectIt++;

            char s[32];
            std::shared_ptr<ISerializable> objectToSerialize(_objects.at(_currentObject));

#ifdef WIN32
            _sprintf_s_l(s, 32, "%08lx", getCLocale(), _currentObject);
#else
            snprintf_l(s, 32, getCLocale(), "%08lx", _currentObject);
#endif
            _current = _current->addElement("object");
            _current->setAttribute("id",    s);
            _current->setAttribute("class", objectToSerialize->serializeClassName());

            _stack.push_back(_current);

            objectToSerialize->serializeObject(this);

            _stack.pop_back();
            _current = _stack[_stack.size() - 1];
        }

        _currentObject = 0;
    }
}

std::shared_ptr<ISerializable> Writer::readObject(const char* name) {
    throw std::exception();
}

size_t Writer::beginCollection(const char* name) {
    _current = getValue(name, true, true);
    _current->setAttribute("type", "collection");

    _stack.push_back(_current);

    _status = Collection;
    _statuses.push_back(Collection);

    _dictionaryPairs.push_back(_dictionaryPair);
    _dictionaryPair = 0;
    return 0;
}

void Writer::endCollection(const char* name) {
    serialize("count", _dictionaryPair, 0, true);

    _stack.pop_back();
    _current = _stack[_stack.size() - 1];

    _statuses.pop_back();
    _status  = _statuses[_statuses.size() - 1];

    _dictionaryPair = _dictionaryPairs[_dictionaryPairs.size() - 1];
    _dictionaryPairs.pop_back();
}

size_t Writer::beginDictionary(const char* name) {
    _current = getValue(name, true, true);
    _current->setAttribute("type", "dictionary");

    _stack.push_back(_current);

    _status = Dictionary;
    _statuses.push_back(Dictionary);

    _dictionaryPairs.push_back(_dictionaryPair);
    _dictionaryPair = 0;
    return 0;
}

void Writer::endDictionary(const char* name) {
    _stack.pop_back();
    _statuses.pop_back();

    size_t count = _dictionaryPair / 2;

    serialize("count", count, 0, true);

    _dictionaryPair = _dictionaryPairs[_dictionaryPairs.size() - 1];
    _current        = _stack          [_stack.size()           - 1];
    _status         = _statuses       [_statuses.size()        - 1];

    _dictionaryPairs.pop_back();
}

void Writer::beginStruct(const char* name, const char* type) {
    _current = getValue(name, true, true);
    _current->setAttribute("type", type);
    _stack.push_back(_current);
}

void Writer::endStruct(const char* name, const char* type) {
    _stack.pop_back();
    _current = _stack[_stack.size() - 1];
}

void Writer::close() {
    size_t count = _objects.size() - 1;

    serialize("count", count, 0, true);
}
